from lexer.token.token_type import TokenType
from lexer.lexer import Lexer
from parse.AST.program import Program
from parse.AST.func_call_statement import FuncCallStatement
from parse.AST.assign_statment import AssignStatment
from parse.AST.while_statment import WhileStatement
from parse.AST.block import Block
from parse.AST.binary_operation import BinaryOperation
from parse.AST.unary_operation import UnaryOperation
from parse.AST.variable import Variable
from parse.AST.number import Number
from parse.AST.func_define_statement import FuncDefineStatement

class Parser:
    def __init__(self,
                 lexer: Lexer):
        self.lexer = lexer
        self.currentToken = lexer.get_next_token()

    def error(self, reason: str):
        raise Exception("解析出错：{0}. 行号：{1}，列号：{2}".format(
            reason, self.lexer.lineNumber, self.lexer.columnNumber
        ))

    def eat(self, type: TokenType):
        if self.currentToken.type != type:
            self.error("期望类型：{0}，实际类型：{1}。".format(
                type, self.currentToken.type
            ))
        else:
            self.currentToken = self.lexer.get_next_token()

    def program(self, program: Program):
        """
        program : declaration program
                | ε
        """
        if self.currentToken.type != TokenType.EOF:
            try:
                self.declaration(program)
                self.program(program)
            except Exception as e:
                self.error(e.args.__str__())

    def declaration(self, program: Program):
        """
        declaration包含全局变量的定义和函数的定义
        declaration : SEMI
                    | tokenType IDENTIFIER declaration_tail
        """
        token = self.currentToken
        if self.currentToken.type == TokenType.SEMI:
            self.eat(TokenType.SEMI)
        else:
            type = self.type()
            name = self.currentToken.name
            self.eat(TokenType.IDENTIFIER)
            self.declaration_tail(type, name, program)

    def type(self) -> TokenType:
        """
        tokenType : KW_VOID
             | KW_INT
             | KW_CHAR
             | KW_BOOL
        """
        if self.currentToken.type in self.typeFirst():
            self.eat(self.currentToken.type)
        else:
            self.error("type类型不对")
        return self.currentToken.type

    def declaration_tail(self, type: TokenType, name: str, program: Program):
        """
        declaration_tail : SEMI
                         | varlist SEMI
                         | LPAREN para RPAREN funtail
        """
        if self.currentToken.type == TokenType.SEMI:
            self.eat(TokenType.SEMI)
            # 单独的变量声明，这是全局变量，代码生成的时候要生成静态数据
            variable = Variable(type, name)
        elif self.currentToken.type == TokenType.LPAREN:
            # 函数声明
            self.eat(TokenType.LPAREN)
            paraList = self.para()
            func = FuncDefineStatement(type, name, paraList)
            self.eat(TokenType.RPAREN)
            self.funtail(func)

    def varlist(self, type: TokenType):
        """
        varlist : COMMA IDENTIFIER varlist
                | ε
        """
        if self.currentToken.type == TokenType.COMMA:
            self.eat(TokenType.COMMA)
            name = self.currentToken.name
            self.eat(TokenType.IDENTIFIER)
            variable = Variable(type, name)
            # 全局变量定义
            self.symbolTable.addVariable(variable)
            self.varlist(type)

    def idtail(self,
               name: str):
        """
        idtail : ASSIGN expr
               | LPAREN para RPAREN
               | ε
        """
        # 函数调用
        if self.currentToken.type == TokenType.LPAREN:
            self.eat(TokenType.LPAREN)
            parameterList = self.para()
            self.eat(TokenType.RPAREN)
            functionCall = FuncCallStatement(parameterList)
            return functionCall
        elif self.currentToken.type == TokenType.LPAREN: # 赋值语句
            self.eat(TokenType.ASSIGN)
            right = self.expr()
            left = self.symbolTable.getVariable(name)
            return AssignStatment(left, right)
        else:
            # 这就是一个expr中的单独的变量
            return Variable(name)

    def para(self) -> list:
        """
        para : tokenType IDENTIFIER paralist
             | ε
        """
        type = self.type()
        name = self.currentToken.name
        self.eat(TokenType.IDENTIFIER)
        variable = Variable(type, name)
        parameterList = self.paralist()
        result = [variable]
        result.extend(parameterList)
        return result

    def paralist(self) -> list:
        """
        paralist : COMMA tokenType IDENTIFIER paralist
                 | ε
        """
        self.eat(TokenType.COMMA)
        type = self.type()
        name = self.currentToken.name
        self.eat(TokenType.IDENTIFIER)
        variable = Variable(type, name)
        result = [variable]
        result.extend(self.paralist())
        return result

    def funtail(self, function: FuncDefineStatement):
        """
        funtail : SEMI
                | block
        """
        if self.currentToken.type == TokenType.SEMI:
            # 函数声明
            self.eat(TokenType.SEMI)
        else:
            # 函数定义
            block = self.block()
            function.block = block

    def block(self):
        """
        block : LBRACK subprogram RBRACK
        """
        self.eat(TokenType.LBRACK)
        block = Block()
        self.subprogram(block)
        self.eat(TokenType.RBRACK)
        return block

    def subprogram(self, block: Block):
        """
        subprogram : localdec subprogram
                   | statements subprogram
                   | ε
        """
        if self.currentToken.type in self.typeFirst():
            self.localdec(block)
            self.subprogram(block)
        elif self.currentToken.type in self.statementFirst():
            self.statements(block)
            self.subprogram(block)
        else:
            raise Exception("无法解析的subprogram")

    def localdec(self, block: Block):
        """
        localdec : tokenType IDENTIFIER localdectail SEMI
        """
        type = self.type()
        name = self.currentToken.name
        self.eat(TokenType.IDENTIFIER)
        variable = Variable(type, name)
        result = [variable]
        result.extend(self.localdectail(type))
        self.eat(TokenType.SEMI)
        block.variables = result

    def localdectail(self, type: TokenType):
        """
        localdectail : COMMA IDENTIFIER localdectail
        """
        self.eat(TokenType.COMMA)
        name = self.currentToken.name
        self.eat(TokenType.IDENTIFIER)
        variable = Variable(type, name)
        results = [variable]
        results.extend(self.localdectail(type))
        return results

    def statements(self, block: Block):
        """
        statements : IDENTIFIER idtail SEMI
                   | whilestat
                   | forstat
                   | dowhilestat
                   | ifstat
                   | switchstat
                   | BREAK SEMI
                   | CONTINUE SEMI
                   | RETURN retstat
                   | SEMI
        """
        if self.currentToken.type == TokenType.KW_WHILE:
            self.whilestat()
        elif self.currentToken.type == TokenType.KW_FOR:
            self.forstat()
        elif self.currentToken.type == TokenType.KW_IF:
            self.ifstat()
        # elif self.currentToken.tokenType == TokenType.KW_SWITCH:
        #     self.switchstat()
        elif self.currentToken.type == TokenType.KW_BREAK:
            self.eat(TokenType.KW_BREAK)
            self.eat(TokenType.SEMI)
        elif self.currentToken.type == TokenType.KW_CONTINUE:
            self.eat(TokenType.KW_CONTINUE)
            self.eat(TokenType.SEMI)
        elif self.currentToken.type == TokenType.KW_RETURN:
            self.eat(TokenType.KW_RETURN)
            self.retstat()
        else:
            name = self.currentToken.name
            self.eat(TokenType.IDENTIFIER)
            self.idtail(name)
            self.eat(TokenType.SEMI)
        return

    def whilestat(self):
        """
        whilestat : WHILE LPAREN expr RPAREN block
        """
        self.eat(TokenType.KW_WHILE)
        self.eat(TokenType.LPAREN)
        expr = self.expr()
        self.eat(TokenType.RPAREN)
        block = self.block()
        while_statement = WhileStatement(expr, block)
        return while_statement

    def forstat(self):
        """
        forstat : KW_FOR LPAREN forinit SEMI altexpr SEMI altexpr RPAREN block
        """
        self.symbolTable.enter()
        self.eat(TokenType.KW_FOR)
        self.eat(TokenType.LPAREN)
        self.forinit()
        self.eat(TokenType.SEMI)
        (_for, _exit) = self.generator.genForHead()
        cond = self.altexpr()
        (_step, _block) = self.generator.genForCondBegin(cond, _exit)
        self.eat(TokenType.SEMI)
        self.altexpr()
        self.eat(TokenType.RPAREN)
        self.block()
        self.generator.genForTail(_step, _exit)
        self.symbolTable.leave()
        return

    def altexpr(self):
        """
        altexpr : expr
                | ε
        """
        if self.currentToken.type in self.exprFirst():
            return self.expr()
        return Variable.getVoid()

    def varrdef(self,
                isExterned: bool,
                type: TokenType,
                isPointer: bool,
                name: str):
        """
        varrdef : LBRACK NUMBER RBRACK
                | init
        """
        if self.currentToken.type == TokenType.LBRACK:
            self.eat(TokenType.LBRACK)
            length = self.currentToken.value
            self.eat(TokenType.NUMBER)
            self.eat(TokenType.RBRACK)
            return Array(self.symbolTable.getScopePath(),
                         isExterned,
                         type,
                         name,
                         length)
        else:
            return self.init(isExterned, type, isPointer, name)

    def expr(self):
        """
        expr : aloexpr exptail
        """
        left = self.aloexpr()
        result = self.exptail(left)
        return result

    def exptail(self, node):
        """
        exptail : GT aloexpr exptail
                | GE aloexpr exptail
                | LT aloexpr exptail
                | LE aloexpr exptail
                | EQUAL aloexpr exptail
                | NOT_EQUAL aloexpr exptail
                | ε
        """
        if self.currentToken.type in (TokenType.GT,
                                      TokenType.GE,
                                      TokenType.LE,
                                      TokenType.LT,
                                      TokenType.EQUAL,
                                      TokenType.NOT_EQUAL):
            self.eat(self.currentToken.type)
            right = self.aloexpr()
            node = BinaryOperation(node, self.currentToken.type, right)
            return self.exptail(node)
        else:
            return node

    def aloexpr(self):
        """
        aloexpr : item itemtail
        """
        variable = self.item()
        return self.itemtail(variable)

    def item(self):
        """
        item : factor factortail
        """
        left = self.factor()
        return self.factortail(left)

    def itemtail(self, node):
        """
        itemtail : ADD aloexp
                 | SUB aloexp
                 | ε
        """
        if self.currentToken.type in (TokenType.ADD, TokenType.SUB):
            op = self.currentToken.type
            self.eat(op)
            right = self.aloexpr()
            return BinaryOperation(node, op, right)
        return node

    def factor(self):
        """
        factor : IDENTIFIER idtail
               | NUMBER
               | CHAR
               | LPAREN expr RPAREN
        """
        if self.currentToken.type == TokenType.IDENTIFIER:
            name = self.currentToken.name
            variable = self.idtail(name)
            return variable
        elif self.currentToken.type == TokenType.NUMBER:
            value = self.currentToken.value
            self.eat(TokenType.NUMBER)
            return Number(value)
        elif self.currentToken.type == TokenType.CHAR:
            value = ord(self.currentToken.value)
            self.eat(TokenType.NUMBER)
            return Number(value)
        else:
            self.eat(TokenType.LPAREN)
            node = self.expr()
            self.eat(TokenType.RPAREN)
            return node

    def factortail(self, node):
        pass

    def val(self) -> Variable:
        """
        val : elem INC
            | elem DEC
            | elem
        """
        variable = self.elem()
        if (self.currentToken.type in (TokenType.INC, TokenType.DEC)):
            return self.generator.genOneOpRight(variable, self.currentToken.type)
        return variable

    def elem(self) -> Variable:
        """
        elem : IDENTIFIER idexpr
             | LPAREN expr RPAREN
             | literal
        """
        if self.currentToken.type == TokenType.IDENTIFIER:
            name = self.currentToken.name
            self.eat(TokenType.IDENTIFIER)
            return self.idexpr(name)
        elif self.currentToken.type == TokenType.LPAREN:
            self.eat(TokenType.LPAREN)
            variable = self.expr()
            self.eat(TokenType.RPAREN)
            return variable
        return self.literal()

    def literal(self) -> Variable:
        """
        literal : NUMBER
                | STRING
                | CHAR
        """
        if self.currentToken.type == TokenType.NUMBER:
            self.eat(TokenType.NUMBER)
            variable = ConstantVariable(self.currentToken)
            self.symbolTable.addVariable(variable)
        elif self.currentToken.type == TokenType.STRING:
            self.eat(TokenType.STRING)
            variable = ConstantVariable(self.currentToken)
            self.symbolTable.addString(variable)
        elif self.currentToken.type == TokenType.CHAR:
            self.eat(TokenType.CHAR)
            variable = ConstantVariable(self.currentToken)
            self.symbolTable.addVariable(variable)
        else:
            variable = None

        self.error("错误的literal")
        return variable

    def idexpr(self, name: str) -> Variable:
        """
        idexpr : LBRACK expr RBRACK
               | LPAREN realarg RPAREN
               | ε
        """
        if self.currentToken.type == TokenType.LBRACK:
            self.eat(TokenType.LBRACK)
            index = self.expr()
            self.eat(TokenType.RBRACK)
            array = self.symbolTable.getVar(name)
            return self.generator.genArray(array, index)  # 产生数组运算表达式
        elif self.currentToken.type == TokenType.LPAREN:
            self.eat(TokenType.LPAREN)
            args = self.realarg()
            self.eat(TokenType.RPAREN)
            function = self.symbolTable.getFun(name, args)
            return self.generator.genCall(function, args)
        return self.symbolTable.getVar(name)

    def realarg(self):
        """
        realarg : arg arglist
                | ε
        """
        if self.currentToken.type in self.exprFirst():
            result = [self.arg()]
            result.extend(self.arglist())
            return result
        else:
            self.error("realarg出错")

    def arglist(self):
        """
        arglist : COMMA arg arglist
                | ε
        """
        if self.currentToken.type == TokenType.COMMA:
            result = [self.arg()]
            result.extend(self.arglist())
            return result
        return []

    def arg(self):
        """
        arg : expr
        """
        return self.expr()

    def typeFirst(self) -> ():
        """
        type的first集
        """
        return (TokenType.KW_INT,
                TokenType.KW_CHAR,
                TokenType.KW_BOOL,
                TokenType.KW_VOID)

    def exprFirst(self) -> tuple:
        return (TokenType.LPAREN,
                TokenType.NUMBER,
                TokenType.CHAR,
                TokenType.STRING,
                TokenType.IDENTIFIER,
                TokenType.NOT,
                TokenType.SUB,
                TokenType.AMPERSAND,
                TokenType.MULTIPLY,
                TokenType.INC,
                TokenType.DEC)

    def statementFirst(self) -> tuple:
        result = [first for first in self.exprFirst()]
        result.extend([TokenType.SEMI,
                          TokenType.KW_WHILE,
                          TokenType.KW_FOR,
                          TokenType.KW_IF,
                          TokenType.KW_SWITCH,
                          TokenType.KW_RETURN,
                          TokenType.KW_BREAK,
                          TokenType.KW_CONTINUE])
        return tuple(result)
